package cn.tqfeiyang.smartadmin.commons.mybatis;

import cn.tqfeiyang.smartadmin.commons.toolbox.BeanUtils;
import cn.tqfeiyang.smartadmin.commons.toolbox.CollectionUtils;
import cn.tqfeiyang.smartadmin.commons.toolbox.StringUtils;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.core.toolkit.Assert;
import com.baomidou.mybatisplus.extension.toolkit.SqlHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSession;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.*;

/**
 * 关联处理类
 *
 * @author tqfeiyang
 * @since 2024/9/27 14:27
 */
@Slf4j
public class Relation {

    /**
     * 最大深度，超过这个深度就不处理了
     */
    public static int MAX_DEEP = 5;

    /**
     * 获取关联数据
     */
    public static <T> T get(T data) {
        if (Objects.nonNull(data)) {
            if (data instanceof List) {
                internalGet((List<?>) data, null, 0);
            } else {
                internalGet(Arrays.asList(data), null, 0);
            }
        }
        return data;
    }

    /**
     * 获取关联数据，只获取响应类中有同名字段的属性
     *
     * @param data
     * @param responseClass
     * @return 关联数据
     */
    public static <T> T get(Object data, Class<T> responseClass) {
        if (Objects.nonNull(data)) {
            internalGet(Arrays.asList(data), responseClass, 0);
            return BeanUtils.map(data, responseClass);
        }
        return null;
    }

    /**
     * 获取关联数据，只获取响应类中有同名字段的属性
     *
     * @param data
     * @param responseClass
     * @return 关联数据
     */
    public static <T> List<T> get(List<?> data, Class<T> responseClass) {
        if (CollectionUtils.isNotEmpty(data)) {
            internalGet(data, responseClass,0);
            return BeanUtils.map(data, responseClass);
        }
        return new ArrayList<>();
    }

    private static void internalGet(List<?> data, Class<?> responseClass, int deep) {
        // 超过了最大的深度，不再处理
        if (deep >= MAX_DEEP) {
            log.debug("获取关联数据的深度操作了最大深度（MAX_DEEP = {}）", MAX_DEEP);
            return;
        }

        // 列表为空，说明没有数据，不再处理
        if (CollectionUtils.isEmpty(data)) {
            return;
        }

        // 获取实体类型，取列表中的第一个
        Class<?> entityClass = data.get(0).getClass();

        for (Field field : entityClass.getDeclaredFields()) {
            // 处理有 @EntityMapping 注解的字段
            EntityMapping annotation = field.getAnnotation(EntityMapping.class);
            if (Objects.isNull(annotation)) {
                continue;
            }

            // 检查响应类中是否存在同名的字段，如果存在则继续获取关联数据
            Field responseField = null;
            if (Objects.nonNull(responseClass)) {
                responseField = ReflectionUtils.findField(responseClass, field.getName());
                if (Objects.isNull(responseField)) {
                    log.debug("响应类 {} 不存在同名字段 {}，不用获取关联数据",
                            responseClass.getName(),
                            field.getName());
                    continue;
                }
            }

            // 实体类的当前字段，如果字段不存在 或者 没有@TableId字段会抛出异常
            Field thisField = StringUtils.isNotBlank(annotation.thisField()) ?
                    findField(entityClass, annotation.thisField()) :
                    findTableIdField(entityClass);

            // 如果注解没有指定 joinClass 属性，则使用属性字段的类型
            Class<?> joinClass = annotation.joinClass();
            if (Object.class.equals(annotation.joinClass())) {
                joinClass = getFieldType(field);
            }

            // 查找 joinField 字段
            Field joinField = StringUtils.isNotBlank(annotation.joinField()) ?
                    findField(joinClass, annotation.joinField()) :
                    findTableIdField(joinClass);

            // 获取关联id值，需要排除null值和重复值
            List<Object> itemList = new ArrayList<>();
            data.stream().forEach(row -> {
                Object thisValue = getFieldValue(row, thisField);
                if (isValidValue(thisValue) && !itemList.contains(thisValue)) {
                    itemList.add(thisValue);
                }
            });

            // 如果关联id列表为空，就不处理
            if (CollectionUtils.isEmpty(itemList)) {
                continue;
            }

            // 关联的实体列表
            final List<Object> joinList;

            // 区分两种情况，一种是交叉关联，另一种是一对一或一对多关联
            if (!Object.class.equals(annotation.crossJoinClass())) {
                Field crossWhereField = StringUtils.isNotBlank(annotation.crossThisField()) ?
                        findField(annotation.crossJoinClass(), annotation.crossThisField()) :
                        findField(annotation.crossJoinClass(), thisField.getName());

                Field crossJoinField = StringUtils.isNotBlank(annotation.crossJoinField()) ?
                        findField(annotation.crossJoinClass(), annotation.crossJoinField()) :
                        findField(annotation.crossJoinClass(), joinField.getName());

                // 查询交叉id列表
                List<?> crossList = queryList(annotation.crossJoinClass(), crossWhereField, itemList);
                if (CollectionUtils.isEmpty(crossList)) {
                    continue;
                }

                // 将corssList转成corssMap
                List<Object> crossItems = new ArrayList<>();
                Map<Object, List<Object>> crossMap = new HashMap<>();
                crossList.stream().forEach(crossObj -> {
                    Object whereValue = getFieldValue(crossObj, crossWhereField);
                    Object joinValue = getFieldValue(crossObj, crossJoinField);
                    if (isValidValue(joinValue)) {
                        if (crossMap.containsKey(whereValue)) {
                            List<Object> list = crossMap.get(whereValue);
                            if (!list.contains(joinValue)) {
                                list.add(joinValue);
                            }
                        } else {
                            ArrayList<Object> list = new ArrayList<>();
                            list.add(joinValue);
                            crossMap.put(whereValue, list);
                        }
                        if (!crossItems.contains(joinValue)) {
                            crossItems.add(joinValue);
                        }
                    }
                });

                // 获取关联实体列表
                joinList = queryList(joinClass, joinField, crossItems);

                // 绑定关联实体
                data.stream().forEach(row -> {
                    bindData(row, joinList, crossMap, field, thisField, joinField);
                });

            } else {
                // 获取关联实体列表
                joinList = queryList(joinClass, joinField, itemList);

                // 绑定关联实体
                data.stream().forEach(row -> {
                    bindData(row, joinList, null, field, thisField, joinField);
                });
            }

            // 继续获取下一层
            if (CollectionUtils.isNotEmpty(joinList)) {
                internalGet(joinList,
                        Objects.nonNull(responseField) ? getFieldType(responseField) : null,
                        deep + 1);
            }
        }
    }

    /**
     * 查找指定名称的字段
     *
     * @param clazz
     * @param name
     * @return 字段
     */
    private static Field findField(Class<?> clazz, String name) {
        Field field = ReflectionUtils.findField(clazz, name);
        if (Objects.isNull(field)) {
            throw new RuntimeException(String.format("字段不存在(实体类=%s，字段名=%s)",
                    clazz.getName(),
                    name));
        }
        return field;
    }

    /**
     * 查找包含 @TableId 的字段
     *
     * @param clazz
     * @return 字段
     */
    private static Field findTableIdField(Class<?> clazz) {
        for (Field field : clazz.getDeclaredFields()) {
            TableId annotation = field.getAnnotation(TableId.class);
            if (Objects.nonNull(annotation)) {
                return field;
            }
        }
        throw new RuntimeException(String.format("不存在包含 @TableId 注解的字段(实体类=%s)",
                clazz.getName()));
    }

    /**
     * 获取字段值
     *
     * @param target
     * @param field
     * @return 字段值
     */
    private static Object getFieldValue(Object target, Field field) {
        try {
            field.setAccessible(true);
            return field.get(target);
        } catch (Exception e) {
            throw new RuntimeException(String.format("获取字段值错误(实体类=%s, 字段名=%s, 字段类型=%s)",
                    target.getClass().getName(),
                    field.getName(),
                    field.getType().getName()), e);
        }
    }

    /**
     * 设置字段值
     *
     * @param target
     * @param field
     * @param value
     */
    private static void setFieldValue(Object target, Field field, Object value) {
        try {
            field.setAccessible(true);
            field.set(target, value);
        } catch (Exception e) {
            throw new RuntimeException(String.format("设置字段值错误(实体类=%s, 字段名=%s, 字段类型=%s, 值类型=%s)",
                    target.getClass().getName(),
                    field.getName(),
                    field.getType().getName(),
                    value != null ? value.getClass().getName() : "null"), e);
        }
    }

    /**
     * 设置字段空值
     *
     * @param target
     * @param field
     */
    private static void setEmptyValue(Object target, Field field) {
        if (List.class.isAssignableFrom(field.getType())) {
            setFieldValue(target, field, new ArrayList<>());
        } else {
            setFieldValue(target, field, null);
        }
    }

    /**
     * 获取实体类的映射接口
     *
     * @param clazz
     * @return 映射接口
     */
    private static <T> BaseMapper<T> getMapper(Class<T> clazz) {
        SqlSession session = SqlHelper.sqlSession(clazz);
        Assert.notNull(session, "mapper not find by class <%s>", clazz.getSimpleName());
        BaseMapper<T> mapper = SqlHelper.getMapper(clazz, session);
        Assert.notNull(mapper, "mapper not find by class <%s>", clazz.getSimpleName());
        return mapper;
    }

    /**
     * 获取字段对应的数据库栏位
     *
     * @param field
     * @return 数据库栏位
     */
    private static String getColumnName(Field field) {
        String columnName = StringUtils.camelToUnderline(field.getName());
        TableId tableIdAnnotation = field.getAnnotation(TableId.class);
        if (Objects.nonNull(tableIdAnnotation) && StringUtils.isNotBlank(tableIdAnnotation.value())) {
            columnName = tableIdAnnotation.value();
        } else {
            TableField tableFieldAnnotation = field.getAnnotation(TableField.class);
            if (Objects.nonNull(tableFieldAnnotation) && StringUtils.isNotBlank(tableFieldAnnotation.value())) {
                columnName = tableFieldAnnotation.value();
            }
        }
        return columnName;
    }

    /**
     * 获取字段类型，如果是Collection，则返回泛型参数
     *
     * @param field
     * @return 字段类型
     */
    private static Class<?> getFieldType(Field field) {
        if (Collection.class.isAssignableFrom(field.getType())) {
            return getGenericType(field);
        } else {
            return field.getType();
        }
    }

    /**
     * 获取泛型参数
     *
     * @param field
     * @return 泛型参数
     */
    private static Class<?> getGenericType(Field field) {
        Type genericType = field.getGenericType();
        if (genericType instanceof ParameterizedType) {
            ParameterizedType pt = (ParameterizedType) genericType;
            Type[] actualTypeArguments = pt.getActualTypeArguments();
            return (Class<?>) actualTypeArguments[0];
        }
        throw new RuntimeException(String.format("无法获取list字段的泛型参数(实体类=%s, 字段名=%s)",
                field.getDeclaringClass().getName(),
                field.getName()));
    }

    /**
     * 执行关联实体查询
     *
     * @param ids
     * @return 关联实体列表
     */
    private static <T> List<Object> queryList(Class<T> clazz, Field whereField, List<Object> ids) {
        QueryWrapper wrapper = new QueryWrapper(clazz);
        wrapper.in(getColumnName(whereField), ids);
        return getMapper(clazz).selectList(wrapper);
    }

    /**
     * 绑定关联实体
     *
     * @param entityObj
     * @param joinList
     * @param crossMap
     * @param propertyField
     * @param thisField
     * @param joinField
     */
    private static void bindData(Object entityObj, List<Object> joinList,
                                 Map<Object, List<Object>> crossMap,
                                 Field propertyField, Field thisField, Field joinField) {
        Object thisValue = getFieldValue(entityObj, thisField);
        if (!isValidValue(thisValue)) {
            setEmptyValue(entityObj, propertyField);
            return;
        }

        List<?> valueList = joinList.stream().filter(joinObj -> {
            Object joinValue = getFieldValue(joinObj, joinField);
            if (Objects.nonNull(crossMap)) {
                List<?> list = crossMap.get(thisValue);
                return CollectionUtils.isNotEmpty(list) && list.contains(joinValue);
            } else {
                return ObjectUtils.nullSafeEquals(joinValue, thisValue);
            }
        }).toList();

        if (CollectionUtils.isNotEmpty(valueList)) {
            if (List.class.isAssignableFrom(propertyField.getType())) {
                setFieldValue(entityObj, propertyField, valueList);
            } else {
                setFieldValue(entityObj, propertyField, valueList.get(0));
            }
        } else {
            setEmptyValue(entityObj, propertyField);
        }
    }

    /**
     * 检查值是否有效
     *
     * 无效的值包括：null，空字符串，Long类型的0，Integer类型的0
     *
     * @param val
     * @return 是否有效
     */
    private static boolean isValidValue(Object val) {
        if (Objects.isNull(val)) {
            return false;
        }
        if (String.class.isAssignableFrom(val.getClass())) {
            return StringUtils.isNotBlank((String) val);
        }
        if (Integer.class.isAssignableFrom(val.getClass())) {
            return ((Integer) val) > 0;
        }
        if (Long.class.isAssignableFrom(val.getClass())) {
            return ((Long) val) > 0;
        }
        return true;
    }
}
